/*
 * Copyright ThinkTank Maths Limited 2006 - 2008
 *
 * This file is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * This file is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this file. If not, see <http://www.gnu.org/licenses/>.
 */
package javax.microedition.location;

import java.io.IOException;
import java.util.Enumeration;
import java.util.Vector;

/**
 * The <code>LandmarkStore</code> class provides methods to store, delete and retrieve
 * landmarks from a persistent landmark store. There is one default landmark store and
 * there may be multiple other named landmark stores. The implementation may support
 * creating and deleting landmark stores by the application. All landmark stores MUST be
 * shared between all J2ME applications and MAY be shared with native applications in the
 * terminal. Named landmark stores have unique names in this API. If the underlying
 * implementation allows multiple landmark stores with the same name, it must present them
 * with unique names in the API e.g. by adding some postfix to those names that have
 * multiple instances in order to differentiate them.
 * <p>
 * Because native landmark stores may be stored as files in a file system and file systems
 * have sometimes limitations for the allowed characters in file names, the
 * implementations MUST support all other Unicode characters in landmark store names
 * except the following list:
 * <ul>
 * <li>0x0000...0x001F control characters</li>
 * <li>0x005C <code>'\'</code></li>
 * <li>0x002F <code>'/'</code></li>
 * <li>0x003A <code>':'</code></li>
 * <li>0x002A <code>'*'</code></li>
 * <li>0x003F <code>'?'</code></li>
 * <li>0x0022 <code>'"'</code></li>
 * <li>0x003C <code>'<'</code></li>
 * <li>0x003E <code>'>'</code></li>
 * <li>0x007C <code>'|'</code></li>
 * <li>0x007F...0x009F control characters</li>
 * <li>0xFEFF Byte-order-mark</li>
 * <li>0xFFF0...0xFFFF</li>
 * </ul>
 * Support for the listed characters is not required and therefore applications are
 * strongly encouraged not to use the characters listed above in landmark store names in
 * order to ensure interoperability of the application on different platform
 * implementations.
 * <p>
 * The <code>Landmark</code>s have a name and may be placed in a category or several
 * categories. The category is intended to group landmarks that are of similar type to the
 * end user, e.g. restaurants, museums, etc. The landmark names are strings that identify
 * the landmark to the end user. The category names describe the category to the end user.
 * The language used in the names may be any and depends on the preferences of the end
 * user. The names of the categories are unique within a <code>LandmarkStore</code>.
 * However, the names of the landmarks are not guaranteed to be unique.
 * <code>Landmark</code>s with the same name can appear in multiple categories or even
 * several <code>Landmark</code>s with the same name in the same category.
 * <p>
 * The <code>Landmark</code> objects returned from the <code>getLandmarks</code>
 * methods in this class shall guarantee that the application can read a consistent set of
 * the landmark data valid at the time of obtaining the object instance, even if the
 * landmark information in the store is modified subsequently by this or some other
 * application.
 * <p>
 * The <code>Landmark</code> object instances can be in two states: <br>
 * <ul>
 * <li>initially constructed by an application </li>
 * <li>belongs to a <code>LandmarkStore</code> </li>
 * </ul>
 * A <code>Landmark</code> object belongs to a <code>LandmarkStore</code> if it has
 * been obtained from the <code>LandmarkStore</code> using <code>getLandmarks</code>
 * or if it has been added to the <code>LandmarkStore</code> using
 * <code>addLandmark</code>. A <code>Landmark</code> object is initially constructed
 * by an application when it has been constructed using the constructor but has not been
 * added to a <code>LandmarkStore</code> using <code>addLandmark</code>.
 * <p>
 * Note that the term "belongs to a <code>LandmarkStore</code>" is defined above in a
 * way that "belong to a <code>LandmarkStore</code>" has an different meaning than the
 * landmark "being currently stored in a <code>LandmarkStore</code>". According to the
 * above definition, a <code>Landmark</code> object instance may be in a state where it
 * is considered to "belong to a <code>LandmarkStore</code>" even when it is not stored
 * in that <code>LandmarkStore</code> (e.g. if the landmark is deleted from the
 * <code>LandmarkStore</code> using <code>deleteLandmark</code> method, the
 * <code>Landmark</code> object instance still is considered to "belong to this
 * <code>LandmarkStore</code>").
 * <p>
 * The landmark stores created by an application and landmarks added in landmark stores
 * persist even if the application itself is deleted from the terminal.
 * <p>
 * Accessing the landmark store may cause a <code>SecurityException</code>, if the
 * calling application does not have the required permissions. The permissions to read and
 * write (including add and delete) landmarks are distinct. An application having e.g. a
 * permission to read landmarks wouldn't necessarily have the permission to delete them.
 * The permissions (names etc.) for the MIDP 2.0 security framework are defined elsewhere
 * in this specification.
 */
public class LandmarkStore {

	/**
	 * Creates a new landmark store with a specified name. All LandmarkStores are shared
	 * between all J2ME applications and may be shared with native applications.
	 * Implementations may support creating landmark stores on a removable media. However,
	 * the Java application is not able to directly choose where the landmark store is
	 * stored, if the implementation supports several storage media. The implementation of
	 * this method may e.g. prompt the end user to make the choice if the implementation
	 * supports several storage media. If the landmark store is stored on a removable
	 * media, this media might be removed by the user possibly at any time causing it to
	 * become unavailable.
	 * <p>
	 * A newly created landmark store does not contain any landmarks.
	 * <p>
	 * Note that the landmark store name MAY be modified by the implementation when the
	 * store is created, e.g. by adding an implementation specific post-fix to
	 * differentiate stores on different storage drives as described in the class
	 * overview. Therefore, the application needs to use the listLandmarkStores method to
	 * discover the form the name was stored as. However, when creating stores to the
	 * default storage location, it is recommended that the implementation does not modify
	 * the store name but preserves it in the form it was passed to this method. It is
	 * strongly recommended that this method is implemented as character case preserving
	 * for the store name.
	 *
	 * @param storeName
	 *            the name of the landmark store to create
	 * @throws NullPointerException
	 *             if the parameter is null
	 * @throws IllegalArgumentException
	 *             if the name is too long or if a landmark store with the specified name
	 *             already exists
	 * @throws IOException
	 *             if the landmark store couldn't be created due to an I/O error
	 * @throws SecurityException
	 *             if the application does not have permissions to create a new landmark
	 *             store
	 * @throws LandmarkException
	 *             if the implementation does not support creating new landmark stores
	 */
	public static void createLandmarkStore(String storeName)
			throws NullPointerException, IllegalArgumentException, IOException,
			LandmarkException, SecurityException {
		try {
			com.openlapi.LandmarkStore.createLandmarkStore(storeName);
		} catch (com.openlapi.LandmarkException e) {
			throw new LandmarkException(e);
		}
	}

	/**
	 * Delete a landmark store with a specified name. All the landmarks and categories
	 * defined in the named landmark store are irrevocably removed. If a landmark store
	 * with the specified name does not exist, this method returns silently without any
	 * error.
	 * <p>
	 * Note that the landmark store names MAY be handled as either case-sensitive or
	 * case-insensitive (e.g. Unicode collation algorithm level 2). Therefore, the
	 * implementation MUST accept the names in the form returned by listLandmarkStores and
	 * MAY accept the name in other variations of character case.
	 *
	 * @param storeName
	 *            the name of the landmark store to delete
	 * @throws NullPointerException
	 *             if the parameter is null (the default landmark store can't be deleted)
	 * @throws IOException
	 *             if the landmark store couldn't be deleted due to an I/O error
	 * @throws SecurityException
	 *             if the appliction does not have permissions to delete a landmark store
	 * @throws LandmarkException
	 *             if the implementation does not support deleting landmark stores
	 */
	public static void deleteLandmarkStore(String storeName)
			throws NullPointerException, IOException, SecurityException,
			LandmarkException {
		try {
			com.openlapi.LandmarkStore.deleteLandmarkStore(storeName);
		} catch (com.openlapi.LandmarkException e) {
			throw new LandmarkException(e);
		}
	}

	/**
	 * Gets a LandmarkStore instance for storing, deleting and retrieving landmarks. There
	 * must be one default landmark store and there may be other landmark stores that can
	 * be accessed by name. Note that the landmark store names MAY be handled as either
	 * case-sensitive or case-insensitive (e.g. Unicode collation algorithm level 2).
	 * Therefore, the implementation MUST accept the names in the form returned by
	 * listLandmarkStores and MAY accept the name in other variations of character case.
	 *
	 * @param storeName
	 *            the name of the landmark store to open. if null, the default landmark
	 *            store will be returned
	 * @return the LandmarkStore object representing the specified landmark store or null
	 *         if a landmark store with the specified name does not exist.
	 * @throws SecurityException
	 *             if the application does not have a permission to read landmark stores
	 */
	public static LandmarkStore getInstance(String storeName)
			throws SecurityException {
		return new LandmarkStore(com.openlapi.LandmarkStore
				.getInstance(storeName));
	}

	/**
	 * Lists the names of all the available landmark stores. The default landmark store is
	 * obtained from getInstance by passing null as the parameter. The null name for the
	 * default landmark store is not included in the list returned by this method. If
	 * there are no named landmark stores, other than the default landmark store, this
	 * method returns null.
	 * <p>
	 * The store names must be returned in a form that is directly usable as input to
	 * getInstance and deleteLandmarkStore.
	 *
	 * @return an array of landmark store names
	 * @throws SecurityException
	 *             if the application does not have the permission to access landmark
	 *             stores
	 * @throws IOException
	 *             if an I/O error occurred when trying to access the landmark stores
	 */
	public static String[] listLandmarkStores() throws SecurityException,
			IOException {
		return com.openlapi.LandmarkStore.listLandmarkStores();
	}

	final com.openlapi.LandmarkStore wrapped;

	/**
	 * Private constructor so that stores may only be created using the static methods of
	 * this class.
	 *
	 * @throws IOException
	 * @throws SecurityException
	 */
	private LandmarkStore(com.openlapi.LandmarkStore wrapped) {
		this.wrapped = wrapped;
	}

	/**
	 * Adds a category to this LandmarkStore. All implementations must support names that
	 * have length up to and including 32 characters. If the provided name is longer it
	 * may be truncated by the implementation if necessary.
	 *
	 * @param categoryName
	 *            name for the category to be added
	 * @throws IllegalArgumentException
	 *             if a category with the specified name already exists
	 * @throws NullPointerException
	 *             if the parameter is null
	 * @throws LandmarkException
	 *             if this LandmarkStore does not support adding new categories
	 * @throws IOException
	 *             if an I/O error occurs or there are no resources to add a new category
	 * @throws SecurityException
	 *             if the application does not have the permission to manage categories
	 */
	public void addCategory(String categoryName)
			throws IllegalArgumentException, NullPointerException,
			LandmarkException, IOException, SecurityException {
		try {
			wrapped.addCategory(categoryName);
		} catch (com.openlapi.LandmarkException e) {
			throw new LandmarkException(e);
		}
	}

	/**
	 * Adds a landmark to the specified group in the landmark store. If some textual
	 * String field inside the landmark object is set to a value that is too long to be
	 * stored, the implementation is allowed to automatically truncate fields that are too
	 * long.
	 * <p>
	 * However, the name field MUST NOT be truncated. Every implementation shall be able
	 * to support name fields that are 32 characters or shorter. Implementations may
	 * support longer names but are not required to. If an application tries to add a
	 * Landmark with a longer name field than the implementation can support,
	 * IllegalArgumentException is thrown.
	 * <p>
	 * When the landmark store is empty, every implementation is required to be able to
	 * store a landmark where each String field is set to a 30 character long string.
	 * <p>
	 * If the Landmark object that is passed as a parameter is an instance that belongs to
	 * this LandmarkStore, the same landmark instance will be added to the specified
	 * category in addition to the category/categories which it already belongs to. If the
	 * landmark already belongs to the specified category, this method returns with no
	 * effect. If the landmark has been deleted after obtaining it from getLandmarks, it
	 * will be added back when this method is called.
	 * <p>
	 * If the Landmark object that is passed as a parameter is an instance initially
	 * constructed by the application using the constructor or an instance that belongs to
	 * a different LandmarkStore, a new landmark will be created in this LandmarkStore and
	 * it will belong initially to only the category specified in the category parameter.
	 * After this method call, the Landmark object that is passed as a parameter belongs
	 * to this LandmarkStore.
	 *
	 * @param landmark
	 *            the landmark to be added
	 * @param category
	 *            where the landmark is added. null can be used to indicate that the
	 *            landmark does not belong to a category
	 * @throws SecurityException
	 *             if the application is not allowed to add landmarks
	 * @throws IllegalArgumentException
	 *             if the landmark has a longer name field than the implementation can
	 *             support or if the category is not null or one of the categories defined
	 *             in this LandmarkStore
	 * @throws IOException
	 *             if an I/O error happened when accessing the landmark store or if there
	 *             are no resources available to store this landmark
	 * @throws NullPointerException
	 *             if the landmark parameter is null
	 */
	public void addLandmark(Landmark landmark, String category)
			throws SecurityException, IllegalArgumentException, IOException,
			NullPointerException {
		wrapped.addLandmark(landmark.wrapped, category);
	}

	/**
	 * Removes a category from this LandmarkStore. The category will be removed from all
	 * landmarks that are in that category. However, this method will not remove any of
	 * the landmarks, only the associated category information from the landmarks. If a
	 * category with the supplied name does not exist in this LandmarkStore, the method
	 * returns silently with no error.
	 *
	 * @param categoryName
	 *            name for the category to be removed
	 * @throws NullPointerException
	 *             if the parameter is null
	 * @throws LandmarkException
	 *             if this LandmarkStore does not support deleting categories
	 * @throws IOException
	 *             if an I/O error occurs
	 * @throws SecurityException
	 *             if the application does not have the permission to manage categories
	 */
	public void deleteCategory(String categoryName)
			throws NullPointerException, LandmarkException, IOException,
			SecurityException {
		try {
			wrapped.deleteCategory(categoryName);
		} catch (com.openlapi.LandmarkException e) {
			throw new LandmarkException(e);
		}
	}

	/**
	 * Deletes a landmark from this LandmarkStore. This method removes the specified
	 * landmark from all categories and deletes the information from this LandmarkStore.
	 * The Landmark instance passed in as the parameter must be an instance that belongs
	 * to this LandmarkStore.
	 * <p>
	 * If the Landmark belongs to this LandmarkStore but has already been deleted from
	 * this LandmarkStore, then the request is silently ignored and the method call
	 * returns with no error. Note that LandmarkException is thrown if the Landmark
	 * instance does not belong to this LandmarkStore, and this is different from not
	 * being stored currently in this LandmarkStore.
	 *
	 * @param lm
	 *            the landmark to be deleted
	 * @throws SecurityException
	 *             if the application is not allowed to delete the landmark
	 * @throws LandmarkException
	 *             if the landmark instance passed as the parameter does not belong to
	 *             this LandmarkStore
	 * @throws IOException
	 *             if an I/O error happened when accessing the landmark store
	 * @throws NullPointerException
	 *             if the parameter is null
	 */
	public void deleteLandmark(Landmark lm) throws SecurityException,
			LandmarkException, IOException, NullPointerException {
		try {
			wrapped.deleteLandmark(lm.wrapped);
		} catch (com.openlapi.LandmarkException e) {
			throw new LandmarkException(e);
		}
	}

	/**
	 * Returns the category names that are defined in this LandmarkStore. The language and
	 * locale used for these names depends on the implementation and end user settings.
	 * The names shall be such that they can be displayed to the end user and have a
	 * meaning to the end user.
	 *
	 * @return an Enumeration containing Strings representing the category names. If there
	 *         are no categories defined in this LandmarkStore, an Enumeration with no
	 *         entries is returned.
	 */
	public Enumeration getCategories() {
		return wrapped.getCategories();
	}

	/**
	 * Lists all landmarks stored in the store.
	 *
	 * @return an Enumeration object containing Landmark objects representing all the
	 *         landmarks stored in this LandmarkStore or null if there are no landmarks in
	 *         the store
	 * @throws IOException
	 *             if an I/O error happened when accessing the landmark store
	 */
	public Enumeration getLandmarks() throws IOException {
		Enumeration lms = wrapped.getLandmarks();
		return wrapOpenLAPIEnumeration(lms);
	}

	/**
	 * Lists all the landmarks that are within an area defined by bounding minimum and
	 * maximum latitude and longitude and belong to the defined category, if specified.
	 * The bounds are considered to belong to the area. If minLongitude <= maxLongitude,
	 * this area covers the longitude range [minLongitude, maxLongitude]. If minLongitude >
	 * maxLongitude, this area covers the longitude range [-180.0, maxLongitude] and
	 * [minLongitude, 180.0).
	 * <p>
	 * For latitude, the area covers the latitude range [minLatitude, maxLatitude].
	 *
	 * @param category
	 *            the category of the landmark. null implies a wildcard that matches all
	 *            categories
	 * @param minLatitude
	 *            minimum latitude of the area. Must be within the range [-90.0, 90.0]
	 * @param maxLatitude
	 *            maximum latitude of the area. Must be within the range [minLatitude,
	 *            90.0]
	 * @param minLongitude
	 *            minimum longitude of the area. Must be within the range [-180.0, 180.0)
	 * @param maxLongitude
	 *            maximum longitude of the area. Must be within the range [-180.0, 180.0)
	 * @return an Enumeration containing all the matching Landmarks or null if no Landmark
	 *         matched the given parameters
	 * @throws IOException
	 *             if an I/O error happened when accessing the landmark store
	 * @throws IllegalArgumentException
	 *             if the minLongitude or maxLongitude is out of the range [-180.0,
	 *             180.0), or minLatitude or maxLatitude is out of the range [-90.0,90.0],
	 *             or if minLatitude > maxLatitude
	 */
	public Enumeration getLandmarks(String category, double minLatitude,
			double maxLatitude, double minLongitude, double maxLongitude)
			throws IOException, IllegalArgumentException {
		Enumeration lms = wrapped.getLandmarks(category, minLatitude,
				maxLatitude, minLongitude, maxLongitude);
		return wrapOpenLAPIEnumeration(lms);
	}

	/**
	 * Gets the Landmarks from the storage where the category and/or name matches the
	 * given parameters.
	 *
	 * @param category
	 *            the category of the landmark. null implies a wildcard that matches all
	 *            categories
	 * @param name
	 *            the name of the desired landmark. null implies a wildcard that matches
	 *            all the names within the category indicated by the category parameter
	 * @return an Enumeration containing all the matching Landmarks or null if no Landmark
	 *         matched the given parameters
	 * @throws IOException
	 *             if an I/O error happened when accessing the landmark store
	 */
	public Enumeration getLandmarks(String category, String name)
			throws IOException {
		Enumeration lms = wrapped.getLandmarks(category, name);
		return wrapOpenLAPIEnumeration(lms);
	}

	/**
	 * Removes the named landmark from the specified category. The Landmark instance
	 * passed in as the parameter must be an instance that belongs to this LandmarkStore.
	 * <p>
	 * If the Landmark is not found in this LandmarkStore in the specified category or if
	 * the parameter is a Landmark instance that does not belong to this LandmarkStore,
	 * then the request is silently ignored and the method call returns with no error. The
	 * request is also silently ignored if the specified category does not exist in this
	 * LandmarkStore.
	 * <p>
	 * The landmark is only removed from the specified category but the landmark
	 * information is retained in the store. If the landmark no longer belongs to any
	 * category, it can still be obtained from the store by passing null as the category
	 * to getLandmarks.
	 *
	 * @param lm
	 *            the landmark to be removed
	 * @param category
	 *            the category from which it will be removed.
	 * @throws SecurityException
	 *             if the application is not allowed to delete the landmark
	 * @throws IOException
	 *             if an I/O error happened when accessing the landmark store
	 * @throws NullPointerException
	 *             if either parameter is null
	 */
	public void removeLandmarkFromCategory(Landmark lm, String category)
			throws SecurityException, IOException, NullPointerException {
		wrapped.removeLandmarkFromCategory(lm.wrapped, category);
	}

	/**
	 * Updates the information about a landmark. This method only updates the information
	 * about a landmark and does not modify the categories the landmark belongs to. The
	 * Landmark instance passed in as the parameter must be an instance that belongs to
	 * this LandmarkStore.
	 * <p>
	 * This method can't be used to add a new landmark to the store.
	 *
	 * @param lm
	 *            the landmark to be updated
	 * @throws SecurityException
	 *             if the application is not allowed to update the landmark
	 * @throws LandmarkException
	 *             if the landmark instance passed as the parameter does not belong to
	 *             this LandmarkStore or does not exist in the store any more
	 * @throws IOException
	 *             if an I/O error happened when accessing the landmark store
	 * @throws NullPointerException
	 *             if the parameter is null
	 * @throws IllegalArgumentException
	 *             if the landmark has a longer name field than the implementation can
	 *             support
	 */
	public void updateLandmark(Landmark lm) throws SecurityException,
			LandmarkException, IOException, NullPointerException,
			IllegalArgumentException {
		try {
			wrapped.updateLandmark(lm.wrapped);
		} catch (com.openlapi.LandmarkException e) {
			throw new LandmarkException(e);
		}
	}

	/**
	 * Converts an Enumeration of com.openlapi.Landmark objects into an Enumeration of
	 * javax.microedition.location.Landmark objects.
	 *
	 * @param landmarks
	 * @return
	 */
	Enumeration wrapOpenLAPIEnumeration(Enumeration landmarks) {
		if (landmarks == null)
			return null;

		Vector lms = new Vector();
		while (landmarks.hasMoreElements()) {
			com.openlapi.Landmark landmark = (com.openlapi.Landmark) landmarks
					.nextElement();
			lms.addElement(new Landmark(landmark));
		}
		return lms.elements();
	}
}
